<!--
 * @Descripttion: 多画面播放组件
 * @version: V1.0.0.1
 * @Author: zhaozhenzhuo
 * @Date: 2025-10-31 09:59:10
 * @LastEditors: zhaozhenzhuo
 * @LastEditTime: 2025-10-31 09:59:10
-->
<template>
	<div class="multi-player-wrapper">
		<!-- 信源列表 -->
		<div v-if="vodList?.length" class="source-list-wrapper">
			<div
				v-for="item in processedVodList"
				:key="item.id"
				class="source-item"
				:class="{ active: currentVodList.some((vod) => vod.id === item.id) }"
				@click="onChangeSourceHandle(item)"
			>
				{{ item.name }}
			</div>
		</div>
		<!-- 多画面容器 -->
		<div class="video-container">
			<div v-if="vodList?.length" class="multi-player-container" ref="multiPlayerContainer"></div>
			<!-- 录播且信源存在多段文件是展示 -->
			<div v-if="isMultiFile" class="right-wrapper">
				<div class="page-block-title">
					<p>回放片段</p>
				</div>
				<div class="vod-list">
					<div v-for="vodItem in vodList" :key="vodItem.sourceCode" class="vod-item">
						<div class="source-wrapper">
							<div class="source-title">
								{{ vodItem.sourceName }}
							</div>
						</div>
						<div class="vod-file-list">
							<div
								v-for="(fileItem, fileIndex) in vodItem.files"
								:key="fileItem.id"
								:class="[
									'file-item',
									currentVodList.some((item) => item.id === fileItem.id) ? 'active' : '',
									!vodItem.files.some((item) => currentVodList.some((i) => i.id === item.id)) &&
									!currentVodList.some((item) => item.id === `${vodItem.sourceCode}_nodata`)
										? 'disabled'
										: ''
								]"
								:title="
									!vodItem.files.some((item) => currentVodList.some((i) => i.id === item.id)) &&
									!currentVodList.some((item) => item.id === `${vodItem.sourceCode}_nodata`)
										? '请在画面列表中添加新画面'
										: ''
								"
							>
								<span class="file-index">{{ fileIndex + 1 }}.</span>
								<div
									class="file-title"
									:title="fileItem.filename"
									@click="onChangeFragmentHandle(fileItem, vodItem)"
								>
									{{ fileItem.filename }}
								</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</div>
		<div v-if="!vodList?.length" class="empty-wrap">
			<p>暂无画面</p>
		</div>
	</div>
</template>

<script setup lang="ts">
// import MultiPlayer from 'multi-player';
// import 'multi-player/dist/style.css';
import MultiPlayer from '@/assets/lib/MultiPlayer';
import { nextTick, onMounted, onUnmounted, ref, watch, computed } from 'vue';
import { ElMessage } from 'element-plus';
import type {
	RecordTaskType,
	FileType,
	VodListItemType,
	UrlListItemType,
	PlayerListOptions
} from './MultiPlayer.type';
import { getErrorMessage, getAudioRecOrderAPI, getVoicePlayConfigAPI } from '@/api/index';

const props = withDefaults(
	defineProps<{
		vodList: RecordTaskType[];
		enableUrlProxy: boolean;
		videoNum: number;
		videoPreviewErrorTis: string;
	}>(),
	{
		vodList: () => [],
		enableUrlProxy: false,
		videoNum: 1, // 初始化的信源个数，最多3个
		videoPreviewErrorTis: '当前无法播放该资源，请检测网络环境或资源地址'
	}
);

const emit = defineEmits<{
	(e: 'currentTimeChange', currentTime: number): void;
	(e: 'seeking'): void;
}>();

// 信源是否拥有多段录像文件
const isMultiFile = computed(() => {
	return props.vodList?.some((item) => item.files.length > 1);
});

watch(
	() => props.vodList,
	(val) => {
		if (val?.length) {
			nextTick(() => {
				init();
			});
		}
	},
	{ immediate: true }
);

onMounted(() => {
	window.addEventListener('resize', resizeHandler);
});

onUnmounted(() => {
	window.removeEventListener('resize', resizeHandler);
});

const resizeHandler = () => {
	setTimeout(() => {
		if (multiPlayer.value && multiPlayerContainer.value) {
			(
				multiPlayer.value as unknown as {
					updatePlayerSize: (width: number, height: number) => void;
				}
			).updatePlayerSize(
				multiPlayerContainer.value.clientWidth,
				multiPlayerContainer.value.clientHeight
			);
		}
	}, 300);
};

// 处理后的信源列表
const processedVodList = ref<VodListItemType[]>([]);
// 当前选择的信源列表
const currentVodList = ref<VodListItemType[]>([]);
// 有声音的信源
const hasVolItem = ref<VodListItemType | null>(null);
// 多画面容器
const multiPlayerContainer = ref<HTMLElement | null>(null);
// 初始化
const init = async () => {
	await Promise.all([getAudioRecOrder(), getVoicePlayConfig()]);
	const _vodList = orderSourceList(props.vodList);
	processedVodList.value = _vodList
		.map((item) => ({
			name: item.sourceName,
			code: item.sourceCode,
			id: item.files[0]?.id || `${item.sourceCode}_nodata`,
			url: changeVideoURl((item.files && item.files[0] && item.files[0].url) || ''),
			deviationTime:
				item.files && item.files[0] && item.files[0].fbeginTimes
					? -item.files[0].fbeginTimes / 1000 // 这里要用负数，因为fbeginTimes越大的说明录的越晚，那么它的进度应该小
					: 0,
			purpose: item.purpose,
			isCovered: item.isCovered,
			isDelete: item.isDelete
		}))
		.filter((item) => item.url);

	// 根据videoNum初始化信源
	currentVodList.value = processedVodList.value.slice(0, props.videoNum > 3 ? 3 : props.videoNum);
	// 获取有声音的信源id
	hasVolItem.value =
		processedVodList.value.find((item) => item.purpose === voiceConfig.value) || null;

	nextTick(() => {
		initMultiPlayer();
	});
};

// 视角顺序
const audioRecOrder = ref<string>('');
// 获取视角顺序
const getAudioRecOrder = async () => {
	try {
		const data = await getAudioRecOrderAPI();
		audioRecOrder.value = data || '1,3,2,4,6,5';
	} catch (err) {
		console.error(getErrorMessage(err));
		audioRecOrder.value = '1,3,2,4,6,5';
	}
};

// 多画面播放时的音频配置
const voiceConfig = ref<number>(0);
// 获取系统多画面播放时的音频设置
const getVoicePlayConfig = async () => {
	try {
		const data = await getVoicePlayConfigAPI();
		voiceConfig.value = data.purpose || 0;
	} catch (err) {
		console.error(getErrorMessage(err));
		voiceConfig.value = 0;
	}
};

// 排序信源列表，使用getAudioRecOrder返回的顺序
// 默认使用老师全景purpose=1>老师特写purpose=3>学生全景purpose=2>学生特写purpose=4>合成画面purpose=6>电脑画面purpose=5
const orderSourceList = (sourceList: RecordTaskType[]) => {
	return sourceList.sort(
		(a, b) =>
			audioRecOrder.value.indexOf(String(a.purpose)) -
			audioRecOrder.value.indexOf(String(b.purpose))
	);
};

// 拦截更改视频地址
const changeVideoURl = (url: string) => {
	if (props.enableUrlProxy && !url.includes('stream?') && url) {
		return `${window.location.protocol}//${window.location.host}/stream?srcUrl=${url}`;
	}
	return `${url}`;
};

// 切换信源
const onChangeSourceHandle = (item: VodListItemType) => {
	const _isDelete = currentVodList.value.some((vod) => vod.id === item.id);
	// 至少选择一个信源
	if (_isDelete && currentVodList.value.length <= 1) {
		ElMessage.warning('至少选择一个信源');
		return;
	}

	// 最多选择3个信源
	if (!_isDelete && currentVodList.value.length >= 3) {
		ElMessage.warning('最多选择3个信源');
		return;
	}

	if (_isDelete) {
		// 从选择信源列表中删除该项
		currentVodList.value = currentVodList.value.filter((vod) => vod.id !== item.id);
	} else {
		// 添加到选择信源列表
		currentVodList.value.push(item);
	}

	initMultiPlayer();
};

// 切换录像片段
const onChangeFragmentHandle = (fileItem: FileType, vodItem: RecordTaskType) => {
	if (
		!vodItem.files.some((item) => currentVodList.value.some((i) => i.id === item.id)) &&
		!currentVodList.value.some((item) => item.id === `${vodItem.sourceCode}_nodata`)
	) {
		return;
	}
	// 点击自己时不走后面
	if (fileItem.id === currentVodList.value.find((item) => item.id === fileItem.id)?.id) {
		return;
	}
	// 统一修改全部当前信源，均采用选择的fileItem的index对应的数据
	processedVodList.value.forEach((item) => {
		const _item = props.vodList.find((i) => i.sourceCode === item.code);
		if (_item) {
			item.id = _item.files[fileItem.index || 0]?.id || `${_item.sourceCode}_nodata`;
			item.url = changeVideoURl(_item.files[fileItem.index || 0]?.url || '');
			item.deviationTime = _item.files[fileItem.index || 0]?.fbeginTimes
				? -_item.files[fileItem.index || 0]?.fbeginTimes / 1000
				: 0;
		}
	});

	initMultiPlayer();
};

// 播放器对象
const multiPlayer = ref<InstanceType<typeof MultiPlayer> | null>(null);
// 初始化多画面播放器
const initMultiPlayer = () => {
	const urlList: UrlListItemType[] = currentVodList.value.map((item) => ({
		id: item.id,
		url: item.url,
		deviationTime: item.deviationTime,
		hasVol: item.id === hasVolItem.value?.id,
		placeholder:
			item.id === `${item.code}_nodata`
				? '暂无画面'
				: item.isDelete
				? '录像文件已被删除，请联系管理人员确认'
				: item.isCovered
				? '录像文件已被覆盖，请联系管理人员确认'
				: ''
	}));

	if (!multiPlayer.value && multiPlayerContainer.value) {
		multiPlayer.value = new MultiPlayer({
			type: 'mp4',
			el: multiPlayerContainer.value,
			width: multiPlayerContainer.value.clientWidth,
			height: multiPlayerContainer.value.clientHeight,
			enableVolumeMemory: true,
			urlList,
			errorTips: props.videoPreviewErrorTis,
			seekingCallback: () => {
				emit('seeking');
			}
		});
	} else if (multiPlayer.value && multiPlayerContainer.value) {
		(
			multiPlayer.value as unknown as { setUrlList: (urlList: UrlListItemType[]) => void }
		).setUrlList(urlList);
	}

	setHeartbeatTime();
};

// 设置心跳
const setHeartbeatTime = () => {
	if (multiPlayer.value) {
		const playerList: PlayerListOptions[] = (
			multiPlayer.value as unknown as { getPlayerList: () => PlayerListOptions[] }
		).getPlayerList();

		(
			multiPlayer.value as unknown as {
				setHeartbeatTime: (
					options: { heartbeatTime: number; heartbeatId: string },
					callback: (currentTime: number) => void
				) => void;
			}
		).setHeartbeatTime(
			{
				heartbeatTime: 100, // 100ms一次心跳
				heartbeatId: hasVolItem.value?.id || playerList.find((item) => item.isLarge)?.id || ''
			},
			(currentTime: number) => {
				// 100ms一次心跳，触发该回调
				emit('currentTimeChange', currentTime);
			}
		);
	}
};

// 跳转视频时间
const changeVideoTime = (time: number) => {
	const playerList: PlayerListOptions[] = (
		multiPlayer.value as unknown as { getPlayerList: () => PlayerListOptions[] }
	).getPlayerList();
	// 获取有声音的画面
	const hasVolPlayer = playerList.find((item) => item.hasVol) || null;

	if (multiPlayer.value) {
		(
			multiPlayer.value as unknown as { setCurrentTime: (id: string | null, time: number) => void }
		).setCurrentTime(hasVolPlayer?.id || null, time);
	}
};

//暴露changeVideoTime方法
defineExpose({
	changeVideoTime
});
</script>

<style lang="scss" scoped>
.multi-player-wrapper {
	position: relative;
	display: flex;
	flex-direction: column;
	padding: 1.6rem 0.7rem 1rem 0;
	width: 100%;
	height: 100%;

	.source-list-wrapper {
		display: flex;
		flex-wrap: nowrap;
		align-items: center;
		gap: 1.6rem;
		margin-bottom: 0.8rem;
		overflow-x: auto;

		.source-item {
			flex-shrink: 0;
			padding: 0 2.2rem;
			height: 3.2rem;
			background-color: rgba($color: $bg-block-color-dark, $alpha: 0.1);
			font-size: $font-size-small-1;
			color: $text-color-secondary;
			text-align: center;
			line-height: 3.2rem;
			border-radius: 0.4rem;
			cursor: pointer;

			&.active {
				color: $text-color-brand;
				background-color: rgba($color: $color-brand, $alpha: 0.12);
			}
		}
	}

	.video-container {
		flex: 1;
		min-height: 0;
		display: flex;
		gap: 2rem;

		.multi-player-container {
			flex: 1;
			min-width: 0;
			height: 100%;
			background-color: #000000;
		}

		.right-wrapper {
			flex-shrink: 0;
			width: 30rem;
			box-sizing: border-box;
			border: 1px solid $border-color-primary;
			position: relative;

			.page-block-title {
				height: 4rem;
				padding-left: 2rem;
				line-height: 4rem;
				font-size: $font-size-base;
				color: $color-brand;
				border-bottom: 1px solid rgba($color: $border-color-primary, $alpha: 0.5);

				p {
					position: relative;
				}

				p::before {
					position: absolute;
					left: -1rem;
					top: 50%;
					display: block;
					content: '';
					width: 0.3rem;
					height: $font-size-small-1;
					background: $color-brand;
					transform: translateY(-50%);
				}
			}

			.vod-list {
				padding: 0 1rem;
				height: calc(100% - 4rem);
				overflow-y: auto;
			}

			.vod-item {
				padding: 0.5rem 0;
				border-bottom: 1px solid $border-color-primary;
			}

			.source-wrapper {
				display: flex;
				align-items: center;
				height: 3rem;
				font-size: $font-size-small-1;
				color: $text-color-regular;

				.source-title {
					height: 3rem;
					line-height: 3rem;
					flex: 1;
					text-overflow: ellipsis;
					white-space: nowrap;
					overflow: hidden;
				}

				.source-title.active {
					color: $color-brand;
				}
			}

			.vod-file-list {
				padding: 0 1rem;
			}

			.file-item {
				display: flex;
				align-items: center;
				height: 3rem;
				font-size: $font-size-small-1;
				color: $text-color-regular;
				cursor: pointer;

				.file-index {
					padding-right: 0.5rem;
				}
				.file-title {
					height: 3rem;
					line-height: 3rem;
					flex: 1;
					min-width: 0;
					text-overflow: ellipsis;
					white-space: nowrap;
					overflow: hidden;
				}
			}

			.file-item.active,
			.file-item:hover {
				color: $color-brand;
			}

			.file-item.disabled {
				color: $text-color-disabled;
				cursor: not-allowed;
			}
		}
	}
}
</style>
